package com.ar4j.spring;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.AutowireCandidateResolver;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.PriorityOrdered;

/**
 * TODO
 */
public class AutowiringBeanFactoryPostProcessor implements AutowireCandidateResolver, BeanFactoryPostProcessor, PriorityOrdered {
  private AutowireCandidateResolver delegate;
  private DefaultListableBeanFactory factory;

  @Override
  public int getOrder() {
    return PriorityOrdered.HIGHEST_PRECEDENCE;
  }
  
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory factoryCandidate) throws BeansException {
    if(!(factoryCandidate instanceof DefaultListableBeanFactory)) {
      throw new IllegalArgumentException("AdapterInjectingAutowireCandidateResolver requires a DefaultListableBeanFactory or its subclass to operate, got: " + factoryCandidate.getClass().getName());
    }
    
    factory = (DefaultListableBeanFactory) factoryCandidate;
    if(factory.getAutowireCandidateResolver() != this) {
      this.delegate = factory.getAutowireCandidateResolver();
      factory.setAutowireCandidateResolver(this);
    }
    
  }
  
  @Override
  public Object getSuggestedValue(DependencyDescriptor descriptor) {
    Object out = null;
    if(descriptor.getDependencyType() == SpringActiveRecordAdapter.class) {
      out = getAdapterToInject(descriptor);
    } else {
      out = delegate.getSuggestedValue(descriptor);
    }
    
    return out;
  }

  @Override
  public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
    return delegate.isAutowireCandidate(bdHolder, descriptor);
  }
  
  /**
   * TODO - document
   */
  private SpringActiveRecordAdapter<?> getAdapterToInject(DependencyDescriptor descriptor) {
    Type type = descriptor.getGenericDependencyType();
    
    // make sure the type can be processed
    if(!(type instanceof ParameterizedType)) {
      throw new IllegalArgumentException("Type must be a parameterized type: " + type);
    }
    
    // extract the base class
    ParameterizedType parameterizedType = (ParameterizedType) type;
    if(parameterizedType.getActualTypeArguments() == null || parameterizedType.getActualTypeArguments().length != 1) {
      throw new IllegalArgumentException("Parameterized type must have one actual type argument: " + parameterizedType);
    }
    Class<?> baseClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
    
    // extract annotations (qualifiers/schema)
    AdapterQualifier adapterAnno = null;
    for(Annotation anno : descriptor.getAnnotations()) {
      if(AdapterQualifier.class.isAssignableFrom(anno.getClass())) {
        adapterAnno = (AdapterQualifier) anno;
      }
    }
    
    return SpringActiveRecordAdapterFactory.getSpringActiveRecordAdapter(factory, baseClass, adapterAnno);
  }

}
